﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using IslandUnit.Exceptions;
using Moq;

namespace IslandUnit.Providers
{
    /// <summary>
    /// Mock provider for Moq.
    /// </summary>
    public class MoqProvider : MockProviderBase
    {
        /// <summary>
        /// Creates the mock object.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <returns>The mock object</returns>
        public override object CreateMockObject<TDependency>()
        {
            return new Mock<TDependency>();
        }

        /// <summary>
        /// Setup the return of the stub.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <typeparam name="TResult">The type of object returned.</typeparam>
        /// <typeparam name="TException">The type of exception to throw.</typeparam>
        /// <param name="mock">The mock object</param>
        /// <param name="method">The target function of the stub.</param>
        public override void MockThrowException<TDependency, TResult, TException>(object mock, Expression<Func<TDependency, TResult>> method)
        {
            ((Mock<TDependency>)mock).Setup(method).Throws<TException>();
        }

        /// <summary>
        /// Setup the return of the stub.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <typeparam name="TResult">The type of object returned.</typeparam>
        /// <typeparam name="TException">The type of exception to throw.</typeparam>
        /// <param name="mock">The mock object</param>
        /// <param name="method">The target function of the stub.</param>
        public override void MockThrowException<TDependency, TResult, TException>(object mock, Expression<Func<TDependency, IList<TResult>>> method)
        {
            ((Mock<TDependency>)mock).Setup(method).Throws<TException>();
        }

        /// <summary>
        /// Setup the return of the stub.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <typeparam name="TResult">The type of object returned.</typeparam>
        /// <typeparam name="TException">The type of exception to throw.</typeparam>
        /// <param name="mock">The mock object</param>
        /// <param name="method">The target function of the stub.</param>
        public override void MockThrowException<TDependency, TResult, TException>(object mock, Expression<Func<TDependency, IEnumerable<TResult>>> method)
        {
            ((Mock<TDependency>)mock).Setup(method).Throws<TException>();
        }

        /// <summary>
        /// Setup the return of the stub.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <typeparam name="TResult">The type of object returned.</typeparam>
        /// <param name="mock">The mock object</param>
        /// <param name="method">The target function of the stub.</param>
        /// <param name="objectToReturn">The object to return.</param>
        public override void MockReturn<TDependency, TResult>(object mock, Expression<Func<TDependency, TResult>> method, TResult objectToReturn)
        {
            ((Mock<TDependency>)mock).Setup(method).Returns(objectToReturn);
        }

        /// <summary>
        /// Setup the return of the stub.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <typeparam name="TResult">The type of object returned.</typeparam>
        /// <param name="mock">The mock object</param>
        /// <param name="method">The target function of the stub.</param>
        /// <param name="objectToReturn">The object to return.</param>
        public override void MockReturn<TDependency, TResult>(object mock, Expression<Func<TDependency, IList<TResult>>> method, IList<TResult> objectToReturn)
        {
            ((Mock<TDependency>)mock).Setup(method).Returns(objectToReturn);
        }

        /// <summary>
        /// Setup the return of the stub.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <typeparam name="TResult">The type of object returned.</typeparam>
        /// <param name="mock">The mock object</param>
        /// <param name="method">The target function of the stub.</param>
        /// <param name="objectToReturn">The object to return.</param>
        public override void MockReturn<TDependency, TResult>(object mock, Expression<Func<TDependency, IEnumerable<TResult>>> method, IEnumerable<TResult> objectToReturn)
        {
            ((Mock<TDependency>)mock).Setup(method).Returns(objectToReturn);
        }

        /// <summary>
        /// Gets the mocked object.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <param name="mock">The mock object.</param>
        /// <returns>The mocked object</returns>
        public override TDependency GetMockObject<TDependency>(object mock)
        {
            return ((Mock<TDependency>)mock).Object;
        }

        /// <summary>
        /// Verifies all the mock expectations setted up in the mocked object.
        /// </summary>
        /// <typeparam name="TDependency">The type of the dependency.</typeparam>
        /// <param name="mockSetups">The mock setups.</param>
        public override void VerifyMockExpectations<TDependency>(IEnumerable<MockSetup<TDependency>> mockSetups)
        {
            foreach (var mockSetup in mockSetups)
            {
                var mock = (Mock<TDependency>)mockSetup.Dependency.Mock;
                
                try
                {
                    if (mockSetup.MethodKind == MethodKind.Action)
                    {
                        mock.Verify((Expression<Action<TDependency>>)mockSetup.Method, mockSetup.Times);
                    }
                    else
                    {
                        mock.Verify((Expression<Func<TDependency, object>>)mockSetup.Method, mockSetup.Times);
                    }
                }
                catch (MockException ex)
                {
                    throw new ExpectationException(ex.Message);
                }
            }
        }

        /// <summary>
        /// Creates an placeholder for a method parameter.
        /// </summary>
        /// <typeparam name="TParameter">The type of the parameter.</typeparam>
        /// <returns>The placeholder for a method parameter.</returns>
        public override TParameter MethodParameter<TParameter>()
        {
            return It.IsAny<TParameter>();
        }
    }
}
